﻿using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using My.Core.Options;
using NewLife;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames;

namespace My.Core.Util
{
	/// <summary>
	///  JWT 加解密
	/// </summary>
	public class JWTEncryption
	{
		//
		// 摘要:
		//     刷新 Token 身份标识
		private static readonly string[] _refreshTokenClaims = new string[5] { "f", "e", "s", "l", "k" };

		/// <summary>
		/// 日期类型的 Claim 类型
		/// </summary>
		private static readonly string[] DateTypeClaimTypes = new[] { JwtRegisteredClaimNames.Iat, JwtRegisteredClaimNames.Nbf, JwtRegisteredClaimNames.Exp };

		/// <summary>
		/// 自动刷新 Token 信息
		/// </summary>
		/// <param name="context"></param>
		/// <param name="httpContext"></param>
		/// <param name="expiredTime"></param>
		/// <param name="refreshTokenExpiredTime"></param>
		/// <param name="tokenPrefix"></param>
		/// <param name="clockSkew"></param>
		/// <returns></returns>
		public static bool AutoRefreshToken(AuthorizationHandlerContext context, DefaultHttpContext httpContext, long? expiredTime = null, int refreshTokenExpiredTime = 43200, string tokenPrefix = "Bearer ", long clockSkew = 5L)
		{
			// 如果验证有效，则跳过刷新
			if (context.User.Identity.IsAuthenticated) return true;

			// 判断是否含有匿名特性
			if (httpContext.GetEndpoint()?.Metadata?.GetMetadata<AllowAnonymousAttribute>() != null) return true;

			// 获取过期Token 和 刷新Token
			var expiredToken = GetJwtBearerToken(httpContext, tokenPrefix: tokenPrefix);
			var refreshToken = GetJwtBearerToken(httpContext, "X-Authorization", tokenPrefix: tokenPrefix);
			if (string.IsNullOrWhiteSpace(expiredToken) || string.IsNullOrWhiteSpace(refreshToken)) return false;

			// 交换新的 Token
			var accessToken = Exchange(expiredToken, refreshToken, expiredTime, clockSkew);
			if (string.IsNullOrWhiteSpace(accessToken)) return false;

			// 读取新的 Token Clamis
			var claims = ReadJwtToken(accessToken)?.Claims;
			if (claims == null) return false;

			// 创建身份信息
			var claimIdentity = new ClaimsIdentity("AuthenticationTypes.Federation");
			claimIdentity.AddClaims(claims);
			var claimsPrincipal = new ClaimsPrincipal(claimIdentity);

			// 设置 HttpContext.User 并登录
			httpContext.User = claimsPrincipal;
			httpContext.SignInAsync(claimsPrincipal);

			string accessTokenKey = "access-token"
				 , xAccessTokenKey = "x-access-token"
				 , accessControlExposeKey = "Access-Control-Expose-Headers";

			// 返回新的 Token
			httpContext.Response.Headers[accessTokenKey] = accessToken;
			// 返回新的 刷新Token
			httpContext.Response.Headers[xAccessTokenKey] = GenerateRefreshToken(accessToken, refreshTokenExpiredTime);

			// 处理 axios 问题
			httpContext.Response.Headers.TryGetValue(accessControlExposeKey, out var acehs);
			httpContext.Response.Headers[accessControlExposeKey] = string.Join(',', StringValues.Concat(acehs, new StringValues(new[] { accessTokenKey, xAccessTokenKey })).Distinct());

			return true;
		}

		/// <summary>
		/// 获取 JWT Bearer Token
		/// </summary>
		/// <param name="httpContext"></param>
		/// <param name="headerKey"></param>
		/// <param name="tokenPrefix"></param>
		/// <returns></returns>
		public static string GetJwtBearerToken(DefaultHttpContext httpContext, string headerKey = "Authorization", string tokenPrefix = "Bearer ")
		{
			string text = httpContext.Request.Headers[headerKey].ToString();
			if (string.IsNullOrWhiteSpace(text))
			{
				return null;
			}

			int length = tokenPrefix.Length;
			if (!text.StartsWith(tokenPrefix, ignoreCase: true, null) || text.Length <= length)
			{
				return null;
			}

			string text2 = text;
			int num = length;
			return text2.Substring(num, text2.Length - num);
		}

		/// <summary>
		/// 通过过期Token 和 刷新Token 换取新的 Token
		/// </summary>
		/// <param name="expiredToken"></param>
		/// <param name="refreshToken"></param>
		/// <param name="expiredTime">过期时间（分钟）</param>
		/// <param name="clockSkew">刷新token容差值，秒做单位</param>
		/// <returns></returns>
		public static string Exchange(string expiredToken, string refreshToken, long? expiredTime = null, long clockSkew = 5L)
		{
			// 交换刷新Token 必须原Token 已过期
			var (_isValid, _, _) = Validate(expiredToken);
			if (_isValid) return default;

			// 判断刷新Token 是否过期
			var (isValid, refreshTokenObj, _) = Validate(refreshToken);
			if (!isValid) return default;

			// 解析 HttpContext
			//var httpContext = GetCurrentHttpContext();

			//// 判断这个刷新Token 是否已刷新过
			//var blacklistRefreshKey = "BLACKLIST_REFRESH_TOKEN:" + refreshToken;
			//var distributedCache = httpContext?.RequestServices?.GetService<IDistributedCache>();

			//// 处理token并发容错问题
			//var nowTime = DateTimeOffset.UtcNow;
			//var cachedValue = distributedCache?.GetString(blacklistRefreshKey);
			//var isRefresh = !string.IsNullOrWhiteSpace(cachedValue);    // 判断是否刷新过
			//if (isRefresh)
			//{
			//	var refreshTime = new DateTimeOffset(long.Parse(cachedValue), TimeSpan.Zero);
			//	// 处理并发时容差值
			//	if ((nowTime - refreshTime).TotalSeconds > clockSkew) return default;
			//}

			// 分割过期Token
			var tokenParagraphs = expiredToken.Split('.', StringSplitOptions.RemoveEmptyEntries);
			if (tokenParagraphs.Length < 3) return default;

			// 判断各个部分是否匹配
			if (!refreshTokenObj.GetPayloadValue<string>("f").Equals(tokenParagraphs[0])) return default;
			if (!refreshTokenObj.GetPayloadValue<string>("e").Equals(tokenParagraphs[2])) return default;
			if (!tokenParagraphs[1].Substring(refreshTokenObj.GetPayloadValue<int>("s"), refreshTokenObj.GetPayloadValue<int>("l")).Equals(refreshTokenObj.GetPayloadValue<string>("k"))) return default;

			// 获取过期 Token 的存储信息
			var jwtSecurityToken = SecurityReadJwtToken(expiredToken);
			var payload = jwtSecurityToken.Payload;

			// 移除 Iat，Nbf，Exp
			foreach (var innerKey in DateTypeClaimTypes)
			{
				if (!payload.ContainsKey(innerKey)) continue;

				payload.Remove(innerKey);
			}

			//// 交换成功后登记刷新Token，标记失效
			//if (!isRefresh)
			//{
			//	distributedCache?.SetString(blacklistRefreshKey, nowTime.Ticks.ToString(), new DistributedCacheEntryOptions
			//	{
			//		AbsoluteExpiration = DateTimeOffset.FromUnixTimeSeconds(refreshTokenObj.GetPayloadValue<long>(JwtRegisteredClaimNames.Exp))
			//	});
			//}

			return Encrypt(payload, expiredTime);
		}

		//
		// 摘要:
		//     验证 Token
		//
		// 参数:
		//   accessToken:
		public static (bool IsValid, JsonWebToken Token, TokenValidationResult validationResult) Validate(string accessToken)
		{

			JwtSettings jwtSettings = new();
			AppSettings.Bind("JwtSettings", jwtSettings);

			if (jwtSettings == null)
			{
				return (false, null, null);
			}

			SigningCredentials signingCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey)), SecurityAlgorithms.HmacSha256Signature);
			TokenValidationParameters tokenValidationParameters = CreateTokenValidationParameters(jwtSettings);
			TokenValidationParameters tokenValidationParameters2 = tokenValidationParameters;
			if (tokenValidationParameters2.IssuerSigningKey == null)
			{
				SecurityKey securityKey = (tokenValidationParameters2.IssuerSigningKey = signingCredentials.Key);
			}

			JsonWebTokenHandler jsonWebTokenHandler = new JsonWebTokenHandler();
			try
			{
				TokenValidationResult tokenValidationResult = jsonWebTokenHandler.ValidateToken(accessToken, tokenValidationParameters);
				if (!tokenValidationResult.IsValid)
				{
					return (false, null, tokenValidationResult);
				}

				JsonWebToken item = tokenValidationResult.SecurityToken as JsonWebToken;
				return (true, item, tokenValidationResult);
			}
			catch
			{
				return (false, null, null);
			}
		}

		//
		// 摘要:
		//     生成Token验证参数
		//
		// 参数:
		//   jwtSettings:
		public static TokenValidationParameters CreateTokenValidationParameters(JwtSettings jwtSettings)
		{
			var key = Encoding.ASCII.GetBytes(jwtSettings.SecretKey);

			var tokenDescriptor = new TokenValidationParameters
			{
				ValidateIssuerSigningKey = true,
				ValidateIssuer = true,
				ValidateAudience = true,
				ValidIssuer = jwtSettings.Issuer,
				ValidAudience = jwtSettings.Audience,
				IssuerSigningKey = new SymmetricSecurityKey(key),
				ValidateLifetime = true,//是否验证Token有效期，使用当前时间与Token的Claims中的NotBefore和Expires对比
				ClockSkew = TimeSpan.FromSeconds(30)
				//RequireExpirationTime = true,//过期时间
			};
			return tokenDescriptor;
		}

		/// <summary>
		/// 读取 Token，不含验证
		/// </summary>
		/// <param name="accessToken"></param>
		/// <returns></returns>
		public static JwtSecurityToken SecurityReadJwtToken(string accessToken)
		{
			var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
			var jwtSecurityToken = jwtSecurityTokenHandler.ReadJwtToken(accessToken);
			return jwtSecurityToken;
		}

		/// <summary>
		/// 生成 Token
		/// </summary>
		/// <param name="payload"></param>
		/// <param name="expiredTime">过期时间（分钟）</param>
		/// <returns></returns>
		public static string Encrypt(IDictionary<string, object> payload, long? expiredTime = null)
		{
			var (Payload, JWTSettings) = CombinePayload(payload, expiredTime);
			return Encrypt(JWTSettings.SecretKey, Payload, SecurityAlgorithms.HmacSha256);
		}

		/// <summary>
		/// 生成 Token
		/// </summary>
		/// <param name="issuerSigningKey"></param>
		/// <param name="payload"></param>
		/// <param name="algorithm"></param>
		/// <returns></returns>
		public static string Encrypt(string issuerSigningKey, IDictionary<string, object> payload, string algorithm = SecurityAlgorithms.HmacSha256)
		{
			// 处理 JwtPayload 序列化不一致问题
			var stringPayload = payload is JwtPayload jwtPayload ? jwtPayload.SerializeToJson() : JsonSerializer.Serialize(payload, new JsonSerializerOptions
			{
				Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
			});
			return Encrypt(issuerSigningKey, stringPayload, algorithm);
		}

		/// <summary>
		/// 生成 Token
		/// </summary>
		/// <param name="issuerSigningKey"></param>
		/// <param name="payload"></param>
		/// <param name="algorithm"></param>
		/// <returns></returns>
		public static string Encrypt(string issuerSigningKey, string payload, string algorithm = SecurityAlgorithms.HmacSha256)
		{
			SigningCredentials credentials = null;

			if (!string.IsNullOrWhiteSpace(issuerSigningKey))
			{
				var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(issuerSigningKey));
				credentials = new SigningCredentials(securityKey, algorithm);
			}

			var tokenHandler = new JsonWebTokenHandler();
			return credentials == null ? tokenHandler.CreateToken(payload) : tokenHandler.CreateToken(payload, credentials);
		}

		/// <summary>
		/// 组合 Claims 负荷
		/// </summary>
		/// <param name="payload"></param>
		/// <param name="expiredTime">过期时间，单位：分钟</param>
		/// <returns></returns>
		private static (IDictionary<string, object> Payload, JwtSettings JWTSettings) CombinePayload(IDictionary<string, object> payload, long? expiredTime = null)
		{
			JwtSettings jwtSettings = new();
			AppSettings.Bind("JwtSettings", jwtSettings);

			var datetimeOffset = DateTimeOffset.UtcNow;

			if (!payload.ContainsKey(JwtRegisteredClaimNames.Iat))
			{
				payload.Add(JwtRegisteredClaimNames.Iat, datetimeOffset.ToUnixTimeSeconds());
			}

			if (!payload.ContainsKey(JwtRegisteredClaimNames.Nbf))
			{
				payload.Add(JwtRegisteredClaimNames.Nbf, datetimeOffset.ToUnixTimeSeconds());
			}

			if (!payload.ContainsKey(JwtRegisteredClaimNames.Exp))
			{
				var minute = expiredTime ?? jwtSettings?.Expire ?? 20;
				payload.Add(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddMinutes(minute).ToUnixTimeSeconds());
			}

			if (!payload.ContainsKey(JwtRegisteredClaimNames.Iss))
			{
				payload.Add(JwtRegisteredClaimNames.Iss, jwtSettings?.Issuer);
			}

			if (!payload.ContainsKey(JwtRegisteredClaimNames.Aud))
			{
				payload.Add(JwtRegisteredClaimNames.Aud, jwtSettings?.Audience);
			}

			return (payload, jwtSettings);
		}

		/// <summary>
		/// 读取 Token，不含验证
		/// </summary>
		/// <param name="accessToken"></param>
		/// <returns></returns>
		public static JsonWebToken ReadJwtToken(string accessToken)
		{
			var tokenHandler = new JsonWebTokenHandler();
			if (tokenHandler.CanReadToken(accessToken))
			{
				return tokenHandler.ReadJsonWebToken(accessToken);
			}

			return default;
		}

		/// <summary>
		/// 生成刷新 Token
		/// </summary>
		/// <param name="accessToken"></param>
		/// <param name="expiredTime">刷新 Token 有效期（分钟）</param>
		/// <returns></returns>
		public static string GenerateRefreshToken(string accessToken, int expiredTime = 43200)
		{
			// 分割Token
			var tokenParagraphs = accessToken.Split('.', StringSplitOptions.RemoveEmptyEntries);

			var s = RandomNumberGenerator.GetInt32(10, tokenParagraphs[1].Length / 2 + 2);
			var l = RandomNumberGenerator.GetInt32(3, 13);

			var payload = new Dictionary<string, object>
			{
				{ "f",tokenParagraphs[0] },
				{ "e",tokenParagraphs[2] },
				{ "s",s },
				{ "l",l },
				{ "k",tokenParagraphs[1].Substring(s,l) }
			};

			return Encrypt(payload, expiredTime);
		}
	}
}
